#! /usr/bin/python
"""
 Module: readermain.py

 Rev 0.9.3-0; Last: 03/27/2013 - Previous: 03/26/2013 (AEM)
   - Incorporate a GUI (this module) as part of DMM Reader.
     Allow execution of DMM Reader by either this
     GUI module or from the terminal. 

 GUI for DMM Reader and Plot DMM.

 This file is part of the DMM Reader project.

 Copyright (C) 2012-2013  Arnold E. Morgan

    DMM Reader is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""

import os
import sys
import datetime
import time
import threading
import subprocess

# *** Custom Modules ***
import readmeter
import cfginit

try:
   import wx
except ImportError:
   print "\nSorry: no implementation of wxPython can be found on your " \
         "system ('%s'). \nPlease install wxPython as descriibed in " \
         "the README.txt file.\n" % os.name
   sys.exit()


class MssgStatusBar(wx.StatusBar):
   """ Create a 2 cell status bar """

   def __init__(self, parent):
      wx.StatusBar.__init__(self, parent)

      self.SetFieldsCount(2)
      self.SetStatusText('Welcome to DMM Reader', 0)
      self.SetStatusWidths([-5, -2])
# -----------------------------------------------------


class ParentFrame(wx.Frame):
   def __init__(self, parent, ID, title):
      wx.Frame.__init__(self, parent, ID, title, size=(1090, 900))

      self.IdlePanelColor = '#2CB7F3'    # Panel color when not measuring
      self.LivePanelColor = '#07BFA6'    # Panel color during measuring process
      self.InitValues = {}
      self.CurrentSessionItem = -1       # Current record in the wx.ListCtrl

      self.MeasType = "ohms"
      self.latestMeasuredVal = "888888888"

      # --------- Initialize StatusBar Text ----------
      self.MainStatusBar = MssgStatusBar(self)
      self.SetStatusBar(self.MainStatusBar)
      CurrentDT = datetime.datetime.now()
      FormattedDT = CurrentDT.strftime("%Y-%m-%d %H:%M:%S")
      self.MainStatusBar.SetStatusText(FormattedDT, 1)
       # ----------------------------------------------

      # ------- Menu ---------
      self.create_menu()
      # ---------------------

      self.set_reader_panels()

      # ---------------- Latest Measurement Panel ----------------
      self.set_latest_measurement_panel()
      self.set_latest_panel_ctrls()

      # ---------------- Session Measurement Panel -----------------
      self.set_session_measurement_panel()
      self.set_session_panel_ctrls()
      #-------------------------------------------------------

      #----------------- Get Cfg Values ----------------------
      RtnStat = False
      RtnMssg = ""
      (RtnStat, self.InitValues, RtnMssg) = cfginit.GetCfgSettings(IsTerminal=False)
      if not RtnStat:
         print "Config file read failed:\n " + RtnMssg
         sys.exit()
      else:
         self.ReadPeriodBox.SetValue(self.InitValues['ReadPeriod'])
         self.PortLblVal.SetLabel(self.InitValues['Port'])
         self.LogFileLblVal.SetLabel(self.InitValues['LogFileName'])
         self.set_plot_values()
   #-------------- End of __init__ class method -----------------

   def InitStatusBar(self):
       self.MainStatusBar = MyStatusBar(self)
       self.SetStatusBar(self.MainStatusBar)
       self.CurrentDT = datetime.datetime.now()
       self.FormattedDT = self.CurrentDT.strftime("%Y-%m-%d %H:%M:%S")
       self.MainStatusBar.SetStatusText(self.FormattedDT,1)

   def create_menu(self):
      """ Create all menu items and bind event methods. """

      menubar = wx.MenuBar()
      Utility = wx.Menu()
      mExit = wx.Menu()
      AboutMenu = wx.Menu()

      self.ExitItem = mExit.Append(wx.ID_EXIT, "E&xit DMM Reader")
      self.AboutItem = AboutMenu.Append(wx.ID_ANY, "About &DMM Reader")

      # Are these needed?
      #self.StartSessionMenu = Utility.Append(wx.ID_ANY, "Start Session")
      #self.EndSessionMenu = Utility.Append(wx.ID_ANY, "End Session")
      #self.ClearSessionReadingsMenu = Utility.Append(wx.ID_ANY, "Clear Session Readings")
      self.PlotMenu = Utility.Append(wx.ID_ANY, "&Plot",
                                     "Toggle Plot Controls",
                                     kind=wx.ITEM_CHECK)

      self.Bind(wx.EVT_MENU, self.on_exit, self.ExitItem)
      self.Bind(wx.EVT_MENU, self.show_about, self.AboutItem)
      self.Bind(wx.EVT_MENU, self.toggle_plot_ctrls, self.PlotMenu)

      menubar.Append(Utility, '&Utility')
      menubar.Append(mExit, 'E&xit')
      menubar.Append(AboutMenu, '&About')
      self.SetMenuBar(menubar)
      #------------------------------------------------------------------------

   def on_exit(self, event):
      """ Exit menu action, ok to shutdown. """
      self.Close(True)
      #------------------------------------------------------------------------

   def set_reader_panels(self):
      """ Define the main left and right control panels
      with their sizers.
      """

      self.vbox0 = wx.BoxSizer(wx.VERTICAL)
      hbox = wx.BoxSizer(wx.HORIZONTAL)

      self.LeftInfoPanel = wx.Panel(self, wx.ID_ANY,
                                       style=wx.SUNKEN_BORDER)
      self.RightInfoPanel = wx.Panel(self, wx.ID_ANY,
                                      style=wx.SUNKEN_BORDER)

      self.LeftInfoPanel.SetBackgroundColour(self.IdlePanelColor)
      self.RightInfoPanel.SetBackgroundColour(self.IdlePanelColor)

      # Both panels get 50% of the available horizontal space. 
      hbox.Add(self.LeftInfoPanel, 1, wx.EXPAND | wx.ALL)
      hbox.Add(self.RightInfoPanel, 1, wx.EXPAND)

      self.vbox0.Add(hbox, 10, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10)
      self.SetAutoLayout(True)
      self.SetSizer(self.vbox0)
      self.Layout()

   def set_latest_measurement_panel(self):
      """ Define top group of controls for the latest measurement panel
      related to meter reading attributes.

      Will only be called at application start up time.
      """

      self.SessionMeasurementLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Latest Measurement',
                                           (160, 10))
      self.SessionMeasurementLbl.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.SessionMeasurementLbl.SetForegroundColour(("#000000"))

      self.latestMeasurementPanel = wx.Panel(self.LeftInfoPanel,
                                            wx.ID_ANY, pos=(30, 40),
                                            size=(450, 110),
                                            style=wx.BORDER_RAISED)

      self.latestMeasurementPanel.SetBackgroundColour("#000000")

      self.latestMeasurementBigLbl = wx.StaticText(self.latestMeasurementPanel,
                                                   -1, self.latestMeasuredVal,
                                                   pos=(300, 21),
                                                   style=wx.ALIGN_CENTER)
      self.set_meas_val_pos()

      self.latestMeasurementBigLbl.SetFont(wx.Font(42, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.latestMeasurementBigLbl.SetForegroundColour("#C0C0C0")

      self.latestMeasurementTinyLbl = wx.StaticText(self.latestMeasurementPanel,
                                                   -1, '', pos=(390, 60), style=wx.ALIGN_CENTER)

      self.latestMeasurementTinyLbl.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.latestMeasurementTinyLbl.SetForegroundColour("#C0C0C0")
      self.set_latest_tiny_label()
   # --------------------------------------------------------------------

   def set_meas_val_pos(self):
      """ Based on the number of digits in the measured value,
      set the horizontal position of the displayed value in the
      latest measurement text control and panel.

      Number will be right justified.
      """

      self.latestMeasurementBigLbl.SetLabel(self.latestMeasuredVal)
      NewXPos = 300
      Offset = 0
      if "." in self.latestMeasuredVal:
         Offset = 20
      NumDigits = len(self.latestMeasuredVal)
      if NumDigits > 2:
         NewXPos = 300 - (40 * (NumDigits - 2))

      self.latestMeasurementBigLbl.Move((NewXPos + Offset, 21))
   # --------------------------------------------------------------------

   def set_latest_tiny_label(self):
      """ Set the measurement value quanity
      ('C', 'vdc', 'vac', 'adc', 'aac', 'ohms',...)
      """

      self.latestMeasurementTinyLbl.SetLabel(self.MeasType)
   # --------------------------------------------------------------------

   def set_session_measurement_panel(self):
      """ Define the top level static label controls inside the session
      measurement panel. Remaining ListCtrl and buttons will be
      defined in another method.

      Will only be called at application start up time.
      """

      self.SessionMeasurementLbl = wx.StaticText(self.RightInfoPanel, wx.ID_ANY,
                                    'Session Measurements',
                                           (170, 10))
      self.SessionMeasurementLbl.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.SessionMeasurementLbl.SetForegroundColour(("#000000"))

      self.SessionMeasurementPanel = wx.Panel(self.RightInfoPanel, wx.ID_ANY,
                                       pos=(25, 38), size=(480, 500),
                                       style=wx.BORDER_RAISED)
      self.SessionMeasurementPanel.SetBackgroundColour("#000000")
   # --------------------------------------------------------------------

   def set_session_panel_ctrls(self):
      """ Define the remaining functional controls in the session 
      measurement panel.
      """

      self.SessionListCtrl = wx.ListCtrl(self.SessionMeasurementPanel, -1, pos=(1, 1),
                                       size=(474, 491),
                                       style=wx.LC_REPORT |
                                       wx.BORDER_SUNKEN | wx.LC_HRULES)

      self.SessionListCtrl.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL))

      #*** List of tuple column assignments ***
      ColData = [(0, 62, "Step"), (1, 415, "Reading")]

      for (ColIndex, ColWidth, ColTitle) in ColData:
         self.SessionListCtrl.InsertColumn(ColIndex, ColTitle)
         self.SessionListCtrl.SetColumnWidth(ColIndex, ColWidth)

      self.StartButton = wx.Button(self.RightInfoPanel, wx.ID_ANY,
                                  label="Start Session", pos=wx.Point(40, 570))
      self.StartButton.Bind(wx.EVT_BUTTON, self.start_reader_session)

      self.EndButton = wx.Button(self.RightInfoPanel, wx.ID_ANY,
                                  label="End Session", pos=wx.Point(220, 570))
      self.EndButton.Bind(wx.EVT_BUTTON, self.end_reader_session)

      self.ClearButton = wx.Button(self.RightInfoPanel, wx.ID_ANY,
                                  label="Clear Session", pos=wx.Point(400, 570))
      self.ClearButton.Bind(wx.EVT_BUTTON, self.clear_session_list_content)
   # --------------------------------------------------------------------

   def set_latest_panel_ctrls(self):
      """ Define all the session related text controls and text boxes
      for the latest Measurement Panel.
      """

      # *** DMM read settings ***
      self.SessionCtrlbox = wx.StaticBox(self.LeftInfoPanel, wx.ID_ANY,
                                       label="  Session Settings  ",
                                       pos=wx.Point(30, 160),
                                       size=wx.Size(450, 200), style=0)

      # --- Max Readings ---
      self.MaxReadingsLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                         'Max Readings', (80, 190))


      self.MaxReadingsLbl.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.MaxReadingsLbl.SetForegroundColour(("#000000"))

      self.MaxReadingsBox = wx.TextCtrl(self.LeftInfoPanel, wx.ID_ANY,
                                   value="", pos=(230, 187), size=(60, 24))
      self.MaxReadingsBox.SetMaxLength(6)
      # --- ---

      # --- End Time ---
      self.EndTimeLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'End Time', (80, 225))

      self.EndTimeLbl.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.EndTimeLbl.SetForegroundColour(("#000000"))

      self.EndTimeBox = wx.TextCtrl(self.LeftInfoPanel, wx.ID_ANY,
                                   value="", pos=(230, 222), size=(60, 24))

      self.EndTimeBox.SetMaxLength(5)

      self.EndTimeNote = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    '(hh:mm)', (300, 225))

      self.EndTimeNote.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.EndTimeNote.SetForegroundColour(("#000000"))
      # --- ---

      # --- Read Period ---
      self.ReadPeriodLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Read Period', (80, 257))

      self.ReadPeriodLbl.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.ReadPeriodLbl.SetForegroundColour(("#000000"))

      self.ReadPeriodBox = wx.TextCtrl(self.LeftInfoPanel, wx.ID_ANY,
                                   value="", pos=(230, 257), size=(60, 24))
      self.ReadPeriodBox.SetMaxLength(6)

      self.ReadPeriodNote = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    '(seconds)', (300, 257))

      self.ReadPeriodNote.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.ReadPeriodNote.SetForegroundColour(("#000000"))
      # --- ---

      # --- Port ---
      self.PortLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Port', (80, 295))

      self.PortLbl.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.PortLbl.SetForegroundColour(("#000000"))

      self.PortLblVal = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    '', (230, 295))

      self.PortLblVal.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.PortLblVal.SetForegroundColour(("#000000"))
      # --- ---

      # --- Log File Name ---
      self.LogFileLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Log File Name', (80, 325))
      #                              'Log File Name', (30, 360))

      self.LogFileLbl.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.LogFileLbl.SetForegroundColour(("#000000"))

      self.LogFileLblVal = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    '', (230, 320))
      #                              '', (180, 360))

      self.LogFileLblVal.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.LogFileLblVal.SetForegroundColour(("#000000"))
      # --- ---

      # *** *** Plot Settings *** ***
      LabelFontSize = 10
      LabelFontColor = ("#FFFF00")
      self.SessionCtrlbox = wx.StaticBox(self.LeftInfoPanel, wx.ID_ANY,
                                       label="  Plot Settings  ",
                                       pos=wx.Point(30, 380),
                                       size=wx.Size(450, 400), style=0)

      # --- Template ---
      self.TemplateLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Template', (80, 400))

      self.TemplateLbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.TemplateLbl.SetForegroundColour(("#000000"))

      self.TemplateVal = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the Template val', (210, 400))

      self.TemplateVal.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.TemplateVal.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- Plot Type ---
      self.PlotTypeLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Plot Type', (80, 425))

      self.PlotTypeLbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.PlotTypeLbl.SetForegroundColour(("#000000"))

      self.PlotTypeVal = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the Plot Type val', (210, 425))

      self.PlotTypeVal.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.PlotTypeVal.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- Sesson Template  ---
      self.SessionTemplateLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Session Template:', (80, 450))

      self.SessionTemplateLbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.SessionTemplateLbl.SetForegroundColour(("#000000"))

      self.SessionTemplateVal = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'OfficeVolts_20130307_a.plt', (210, 450))

      self.SessionTemplateVal.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.SessionTemplateVal.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- Output Graph Name ---
      self.OutputGraphLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Graph Name', (80, 475))

      self.OutputGraphLbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.OutputGraphLbl.SetForegroundColour(("#000000"))

      self.OutputGraphVal = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the Graph Name val', (210, 475))

      self.OutputGraphVal.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.OutputGraphVal.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- Plot Title ---
      self.PlotTitleLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Plot Title', (80, 500))

      self.PlotTitleLbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.PlotTitleLbl.SetForegroundColour(("#000000"))

      self.PlotTitleVal = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the Plot Title val', (210, 500))

      self.PlotTitleVal.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.PlotTitleVal.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- X1 Label ---
      self.X1Lbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'X Label', (80, 525))

      self.X1Lbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.X1Lbl.SetForegroundColour(("#000000"))

      self.X1Val = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the X Label val', (210, 525))

      self.X1Val.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.X1Val.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- Y1 Label ---
      self.Y1Lbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Y1 Label', (80, 550))

      self.Y1Lbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.Y1Lbl.SetForegroundColour(("#000000"))

      self.Y1Val = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the Y1 Label val', (210, 550))

      self.Y1Val.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.Y1Val.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- Y2 Label ---
      self.Y2Lbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Y2 Label', (80, 575))

      self.Y2Lbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.Y2Lbl.SetForegroundColour(("#000000"))

      self.Y2Val = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the Y2 Label val', (210, 575))

      self.Y2Val.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.Y2Val.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- Key1 Label---
      self.Key1Lbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Key 1 Label', (80, 600))

      self.Key1Lbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.Key1Lbl.SetForegroundColour(("#000000"))

      self.Key1Val = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the Key 1 Label val', (210, 600))

      self.Key1Val.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.Key1Val.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- Key2 Label---
      self.Key2Lbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Key 2 Label', (80, 625))

      self.Key2Lbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.Key2Lbl.SetForegroundColour(("#000000"))

      self.Key2Val = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the Key 2 Label val', (210, 625))

      self.Key2Val.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.Key2Val.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- Plot 1 Color ---
      self.Plot1ColorLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Plot 1 Color', (80, 650))

      self.Plot1ColorLbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.Plot1ColorLbl.SetForegroundColour(("#000000"))

      self.Plot1ColorVal = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the Plot 1 Color val', (210, 650))

      self.Plot1ColorVal.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.Plot1ColorVal.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- Plot 2 Color ---
      self.Plot2ColorLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Plot 2 Color', (80, 675))

      self.Plot2ColorLbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.Plot2ColorLbl.SetForegroundColour(("#000000"))

      self.Plot2ColorVal = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the Plot 2 Color val', (210, 675))

      self.Plot2ColorVal.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.Plot2ColorVal.SetForegroundColour(LabelFontColor)
      # --- ---

      # --- Image Viewer ---
      self.ImageViewerLbl = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'Image Viewer', (80, 700))

      self.ImageViewerLbl.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.NORMAL))
      self.ImageViewerLbl.SetForegroundColour(("#000000"))

      self.ImageViewerVal = wx.StaticText(self.LeftInfoPanel, wx.ID_ANY,
                                    'the Image Viewer val', (210, 700))

      self.ImageViewerVal.SetFont(wx.Font(LabelFontSize, wx.SWISS, wx.NORMAL, wx.BOLD))
      self.ImageViewerVal.SetForegroundColour(LabelFontColor)
      # --- ---

      self.StartPlotButton = wx.Button(self.LeftInfoPanel, wx.ID_ANY,
                                  label="Do Plot", pos=wx.Point(350, 735))
      self.StartPlotButton.Bind(wx.EVT_BUTTON, self.do_plot_action)

      # Don't show the plot attributes at first start up.
      self.hide_plot_ctrls()
   # *** End of class ParentFrame ***
   # --------------------------------------------------------------------

   def toggle_plot_ctrls(self, event):
      """ Turn plot controls visiblity on or off. """

      if self.PlotMenu.IsChecked():
         # Menu item is now checked
         self.show_plot_ctrls()
      else:
         # Menu item is now unchecked
         self.hide_plot_ctrls()
   #------------------------------------------------------------------------

   def show_plot_ctrls(self):
      """ Ready to plot, show plot related data. """

      self.SessionCtrlbox.Show(True)

      self.TemplateLbl.Show(True)
      self.TemplateVal.Show(True)

      self.PlotTypeLbl.Show(True)
      self.PlotTypeVal.Show(True)

      self.SessionTemplateLbl.Show(True)
      self.SessionTemplateVal.Show(True)

      self.OutputGraphLbl.Show(True)
      self.OutputGraphVal.Show(True)

      self.PlotTitleLbl.Show(True)
      self.PlotTitleVal.Show(True)

      self.X1Lbl.Show(True)
      self.X1Val.Show(True)

      self.Y1Lbl.Show(True)
      self.Y1Val.Show(True)

      self.Y2Lbl.Show(True)
      self.Y2Val.Show(True)

      self.Key1Lbl.Show(True)
      self.Key1Val.Show(True)
      self.Key2Lbl.Show(True)
      self.Key2Val.Show(True)

      self.Plot1ColorLbl.Show(True)
      self.Plot1ColorVal.Show(True)
      self.Plot2ColorLbl.Show(True)
      self.Plot2ColorVal.Show(True)

      self.ImageViewerLbl.Show(True)
      self.ImageViewerVal.Show(True)

      self.StartPlotButton.Show(True)
   # ---------------------------------------------------

   def hide_plot_ctrls(self):
      """ Not Ready to plot, hide plot related data. """

      self.SessionCtrlbox.Show(False)

      self.TemplateLbl.Show(False)
      self.TemplateVal.Show(False)

      self.PlotTypeLbl.Show(False)
      self.PlotTypeVal.Show(False)

      self.SessionTemplateLbl.Show(False)
      self.SessionTemplateVal.Show(False)

      self.OutputGraphLbl.Show(False)
      self.OutputGraphVal.Show(False)

      self.PlotTitleLbl.Show(False)
      self.PlotTitleVal.Show(False)

      self.X1Lbl.Show(False)
      self.X1Val.Show(False)

      self.Y1Lbl.Show(False)
      self.Y1Val.Show(False)

      self.Y2Lbl.Show(False)
      self.Y2Val.Show(False)

      self.Key1Lbl.Show(False)
      self.Key1Val.Show(False)
      self.Key2Lbl.Show(False)
      self.Key2Val.Show(False)

      self.Plot1ColorLbl.Show(False)
      self.Plot1ColorVal.Show(False)
      self.Plot2ColorLbl.Show(False)
      self.Plot2ColorVal.Show(False)

      self.ImageViewerLbl.Show(False)
      self.ImageViewerVal.Show(False)

      self.StartPlotButton.Show(False)
   # ----------------------------------------------------

   def set_plot_values(self):
      """ Set plot values from the cfg file """

      # *** FUTURE UPDATE NOTE: Add a method to update these values
      #     from the GUI without having to restart DMM Reader.

      self.TemplateVal.SetLabel(self.InitValues['Template'])
      self.PlotTypeVal.SetLabel(self.InitValues['PlotType'])
      self.SessionTemplateVal.SetLabel(self.InitValues['SessionTemplate'])
      self.OutputGraphVal.SetLabel(self.InitValues['OutputGraphName'])

      # Keep things neat; if title is too long, truncate the value at
      # 30 characters and concatenate "..." to indicate a partial value.
      # The full value will still be used with plotting.
      LabelName = self.InitValues['PlotTitle']
      if len(LabelName) > 30:
         LabelName = self.InitValues['PlotTitle'][0:30] + "..."
      self.PlotTitleVal.SetLabel(LabelName)

      self.X1Val.SetLabel(self.InitValues['X1Label'])
      self.Y1Val.SetLabel(self.InitValues['Y1Label'])
      self.Y2Val.SetLabel(self.InitValues['Y2Label'])
      self.Key1Val.SetLabel(self.InitValues['Key1Title'])
      self.Key2Val.SetLabel(self.InitValues['Key2Title'])
      self.Plot1ColorVal.SetLabel(self.InitValues['Plot1Color'])
      self.Plot2ColorVal.SetLabel(self.InitValues['Plot2Color'])
      self.ImageViewerVal.SetLabel(self.InitValues['ImageViewer'])
   # ---------------------------------------------------------

   def do_plot_action(self, event):
      """ Run the PlotDMM createplot module independently of this appl. 

      Create the plot file and show with the requested image viewer.
      The graphic file will be a command line parameter to the viewer.

      Module createplot is **assumed** to be located in the directory PlotDMM
      which must be located directly below the DMM Reader install directory.
      """

      CmdStr = ['PlotDMM/createplot.py']
      StdOutData = ""
      RunResponse = (False, "")

      try:
         ImgSubProc = subprocess.Popen(CmdStr, stdout=subprocess.PIPE,
                                       stderr=subprocess.STDOUT)

         StdOutData = ImgSubProc.stdout.read()
         if len(StdOutData) == 0:
            RunResponse = (True, "")
         else:
            RunResponse = (False, StdOutData)

      except OSError, err:
         FailFlag = True
         Mssg = "Cannot start %s process\n\nSubprocess error: %s" % \
                (CmdStr, err)
         RunResponse = (False, Mssg)
      except:
         FailFlag = True
         Mssg = "Unexpected %s subprocess start error: %s" % \
                (CmdStr, sys.exc_info()[0])
         RunResponse = (False, Mssg)

      if RunResponse[0] == True:
         print "\nPlot execution error\n\n" + RtnValue[1]
      else:
         print "\n" + RunResponse[1]
   # ------------------ End of method do_plot_action ---------------

   def start_reader_session(self, event):
      """ Begin a meter reading session.

      Run the reader module in a thread so we can retain
      control of the GUI.
      """

      # Get End time.
      # If set, make available to readmeter module.
      SessionEndTime = self.EndTimeBox.GetValue()
      self.InitValues['EndTime'] = SessionEndTime.strip()

      # Get max number of readings.
      # If set, make available to readmeter module.
      TempSessionReadCnt = self.MaxReadingsBox.GetValue()
      try:
         SessionReadCnt = int(TempSessionReadCnt.strip())
      except ValueError:
         SessionReadCnt = 0 
      self.InitValues['ReadCnt'] = SessionReadCnt

      # Check if read period text box differs from the value in
      # the cfg file. Is so, make sure the value is a valid integer
      # and reset the value of InitValues['ReadPeriod'].
      # Actually, I did that in reverse order.
      TempSessionReadPeriod = self.ReadPeriodBox.GetValue()
      try:
         SessionReadPeriod = int(TempSessionReadPeriod.strip())
      except ValueError:
         SessionReadPeriod = 0 

      if SessionReadPeriod != 0 and SessionReadPeriod <> self.InitValues['ReadPeriod']:
         self.InitValues['ReadPeriod'] = SessionReadPeriod

      readmeter.StopTheSession = False
      self.disable_session_action_ctrls()
      SessionArgs = (self, False, self.InitValues)

      CurrentDT = datetime.datetime.now()
      FormattedDT = CurrentDT.strftime("%Y-%m-%d %H:%M:%S")
      ThreadCnt = threading.activeCount()
      StartMssg = FormattedDT + " Meter reading session in progress...(" + str(ThreadCnt) + ")"
      self.MainStatusBar.SetStatusText(StartMssg, 0)

      # Need to run the reader function in a thread to retain control
      # of the UI. This very simple usage does the trick.
      self.StreamChild = threading.Thread(target=readmeter.main_session_mgr,
                                          name="ReadMeterSession",
                                          args=SessionArgs)
      self.StreamChild.start()
   #------------------------------------------------------------------------

   def end_reader_session(self, event):
      """ Signal to the reader thread that we're done. End Session means
      gracefully, but firmly, gather all your children
      and prepare to end your day at the beach, we gotta head for home.
      """

      readmeter.StopTheSession = True
      EndMssg = ""
      RightNow = time.time()
      WaitDelay = RightNow + int(self.InitValues['ReadPeriod'])
      ThreadCnt = threading.activeCount()
      while threading.activeCount() > 1:
         CurrentDT = datetime.datetime.now()
         FormattedDT = CurrentDT.strftime("%Y-%m-%d %H:%M:%S")
         self.MainStatusBar.SetStatusText("Waiting to end session: " + \
                                          FormattedDT + " 0 - ThreadCnt: " +
                                          str(threading.activeCount()), 0)
         if time.time() > WaitDelay:
            self.MainStatusBar.SetStatusText(FormattedDT + " 1 - ThreadCnt: " +
                                             str(threading.activeCount()), 0)
            EndMssg = "Child thread is not responding to the shutdown request"
            EndMssg += "\nDMM will now end"
            wx.MessageBox(EndMssg, 'Info', wx.OK | wx.ICON_INFORMATION)
            break

      self.enable_session_action_ctrls()
      self.MainStatusBar.SetStatusText("Meter reading session has ended - " + \
                                       str(threading.activeCount()), 0)
      #------------------------------------------------------------------------

   def disable_session_action_ctrls(self):
      """ Reader session starting, disable all controls so we don't do
      mean things while the thread and subprocess are active.
      """

      # We may use the later.
      #self.StartSessionMenu.Enable(False)
      #self.EndSessionMenu.Enable(False)
      #self.ClearSessionReadingsMenu.Enable(False)
      self.PlotMenu.Enable(False)

      self.ExitItem.Enable(False)
      self.AboutItem.Enable(False)

      self.MaxReadingsBox.Enable(False)
      self.EndTimeBox.Enable(False)
      self.ReadPeriodBox.Enable(False)

      self.StartButton.Enable(False)
      self.ClearButton.Enable(False)

      self.StartPlotButton.Enable(False)

      self.LeftInfoPanel.SetBackgroundColour(self.LivePanelColor)
      self.RightInfoPanel.SetBackgroundColour(self.LivePanelColor)
   # -------------------------------------------------------------------------

   def enable_session_action_ctrls(self):
      """ Reader session ending, re-enable all controls; we can now
      do whatever mean thing is allowed.
      """

      #self.StartSessionMenu.Enable(True)
      #self.EndSessionMenu.Enable(True)
      #self.ClearSessionReadingsMenu.Enable(True)
      self.PlotMenu.Enable(True)

      self.ExitItem.Enable(True)
      self.AboutItem.Enable(True)

      self.MaxReadingsBox.Enable(True)
      self.EndTimeBox.Enable(True)
      self.ReadPeriodBox.Enable(True)

      self.StartButton.Enable(True)
      self.ClearButton.Enable(True)

      self.StartPlotButton.Enable(True)

      self.LeftInfoPanel.SetBackgroundColour(self.IdlePanelColor)
      self.RightInfoPanel.SetBackgroundColour(self.IdlePanelColor)
      #------------------------------------------------------------------------

   def clear_session_list_content(self, event):
      """ If anything in the session list ctrl,
      empty it. """

      if self.SessionListCtrl.GetItemCount() > 0:
         self.CurrentSessionItem = -1
         self.SessionListCtrl.DeleteAllItems()
   # -------------------------------------------------------------------------

   def update_lastest_measurement(self, MeterVal, MeterUnit):
      """ Update the latest reading display text (duh!). """

      if len(MeterUnit) > 0:
         self.MeasType = MeterUnit
         self.set_latest_tiny_label()

      if len(MeterVal) > 0:
         self.latestMeasuredVal = MeterVal
         self.set_meas_val_pos()
   # -------------------------------------------------------------------------

   def insert_new_session_list_item(self, RowStr):
      """ Add a new row and item to the SessionList wx.ListCtrl. """

      self.CurrentSessionItem += 1
      self.SessionListCtrl.InsertStringItem(self.CurrentSessionItem,
                                          str(self.CurrentSessionItem))

      self.SessionListCtrl.SetStringItem(self.CurrentSessionItem, 1, RowStr)
   # -------------------------------------------------------------------------

   def update_session_list_item(self, RowStr):
      """ Update the current row of the SessionList wx.ListCtrl. """

      if self.SessionListCtrl.GetItemCount() > 0:
         self.SessionListCtrl.SetStringItem(self.CurrentSessionItem, 1, RowStr)
      else:
         self.insert_new_session_list_item(self, RowStr)
   # -------------------------------------------------------------------------

   def show_about(self, event):
      """ Show the about dialog box """

      OnAboutBox()
   # -------------------------------------------------------------------------

def OnAboutBox():
   """ About dialog box.

   Example from http://zetcode.com/wxpython/dialogs/
   """

   description = "DMM Reader is, at this point, a moderately streamlined " \
                 "evolving general purpose application for the Linux " \
                 "environment that captures measured values from the " \
                 "Mastech  MAS-345 auto ranging digital multiplier. " \
                 "DMM Reader can accommodate any of the measurement " \
                 "type functions provided by the Mastech multimeter. \n\n" \
                 "Output results are written to stdout, the GUI session " \
                 "list control and, optionally, to a log file. The log " \
                 "file subsequently " \
                 "becomes an import data source to the gnuPlot utility. " \
                 "Plotting functions are provided by a separate set of " \
                 "Python modules but exist as an integral part of this " \
                 "project."

   licence = """Both DMM Reader and Plot DMM are free software; you can
redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either version 3 of
the License, or (at your option) any later version.

DMm Reader and Plot DMM are distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License 
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

   info = wx.AboutDialogInfo()

   #info.SetIcon(wx.Icon('gnsimage1.png', wx.BITMAP_TYPE_PNG))
   info.SetName('DMM Reader')
   info.SetVersion('0.9.3-0 (Beta)')
   info.SetDescription(description)
   info.SetCopyright('(C) 2012 - 2013 Arnold Morgan')
   info.SetWebSite(('http://www.byartandbylabor.com', 'ByArtAndByLabor.com'))
   info.SetLicence(licence)
   info.AddDeveloper('Arnold Morgan')
   info.AddDocWriter('Arnold Morgan')

   #info.AddArtist('Arnold Morgan')
   #info.Developers = ["Arnold Morgan"]
   #info.SetArtists(['Arnold Morgan','and others'])
   #info.AddTranslator('Arnold Morgan')

   wx.AboutBox(info)
# ************** End of fcn OnAboutBox *******************

class MainProg(wx.App):
   def OnInit(self):
      self.MainFrame = ParentFrame(None, -1, "DMM Reader")
      self.MainFrame.Centre()
      self.MainFrame.Show()
      return True

def reader_main():
   """ First thing to do; establish the application default
   operating directory.

   To support starting readermain.py via a launcher, make sure the
   location of this module is the current working directory.
   Otherwise, right off the bat we won't be able to find
   the operating state files, or save state files to the 
   correct place.
   """

   #print "Platform: %s, OS Name: %s" % (sys.platform, os.name)
   thisDir = os.path.dirname(os.path.abspath(sys.argv[0]))
   #print "current directory is: " + thisDir
   os.chdir(thisDir)


if __name__ == "__main__":
   reader_main()
   app = MainProg(0)
   app.MainLoop()
